package main

import (
	"database/sql"
	"fmt"
	"log"
	"regexp"
	"strings"
	"time"

	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

func main() {
	// 连接数据库的配置
	p := &conf{
		User:     "root",
		Password: "root",
		Name:     "demo",
		Host:     "192.250.186.213",
		Port:     "5432",
	}

	// 连接数据库
	db, err := Connect(p)
	if nil != err {
		fmt.Println("数据库连接失败")
		return
	}

	// 批量处理数据
	err = databaseOperator1(db)
	if nil != err {
		fmt.Println("执行出错", err)
		return
	}

	fmt.Println("执行完毕")
}

// =============================核心逻辑方法=============================================

// 数据库操作函数
func databaseOperator1(db *gorm.DB) error {
	// 一次批处理50条记录
	var batch int = 50

	res := make([]*Lab, 0, batch)
	var err error

	// 开启事务
	db = db.Begin()
	defer func() {
		if err != nil {
			db.Rollback()
		} else {
			db.Commit()
		}
	}()

	// 执行批处理
	r := db.Model(&Lab{}).FindInBatches(&res, batch, func(tx *gorm.DB, batch int) error {
		// 批量处理找到的记录
		for i, result := range res {
			// res[i].Step = UpdateTheStep(result.Step) // 静态匹配
			res[i].Step = UpdateTheStepWhtiRegexp(result.Step) // 正则表达式匹配
		}

		err := tx.Save(&res).Error // 更新到数据库 --> 替换就行
		return err
	})

	fmt.Println("批处理影响的记录数：", r.RowsAffected) // 整个批量操作影响的记录数
	return err
}

// 封装使用正则表达式做替换的方法
func ReplaceStringByRegex(str, rule, replace string) string {
	reg := regexp.MustCompile(rule) // 正则表达式解析失败会直接panic
	return reg.ReplaceAllString(str, "$1$3")
}

// ===================================辅助方法===========================================

// 静态匹配
func UpdateTheStep(src string) string {
	sep := `<img src=\"`                                             // 用来替换的字符串
	r := strings.Split(src, `<img src=\"http://192.168.129.40:9001`) // 分割字符串
	return strings.Join(r, sep)                                      // 组合
}

// 正则表达式匹配
func UpdateTheStepWhtiRegexp(src string) string {

	// 【匹配img标签中的src】 : `<img[^>]*?src\s*=\\s*[""']?(匹配出域名网址)[ '""][^>]*?>`
	// + 【匹配出域名网址（带端口）】: `(?:http|https)?://[\d]+(?:\.[\d]+)+:\d{1,5}`
	// = `<img[^>]*?src\s*=\\s*[""']?((?:http|https)?://[\d]+(?:\.[\d]+)+:\d{1,5})[^>]*?>`
	// 可以匹配出img中的src=\中的（http或者https）IP地址

	/* `(<img[^>]*?src\s*=\\s*[""']?)((?:http|https)?://[\d]+(?:\.[\d]+)+:\d{1,5})([^>]*?>)`
	//  <img src=\"http://192.168.129.40:9001/dasfffff" width="100" height="50" />
			===》$2: http://192.168.129.40:9001
	//	<img style="width:100px;height:50px" src=\"http://192.168.129.40:9001/dfdfd.1234" />
			===》$2: http://192.168.129.40:9001
	*/
	// regGetImgSrcRule := `(<\s*img[^>]*?src\s*=\\s*[""']?)((?:http|https)?://[\d]+(?:\.[\d]+)+:\d{1,5})([^>]*?>)`
	// src 不止img，还有video
	regGetImgSrcRule := `(<\s*[^>]*?src\s*=\\s*[""']?)((?:http|https)?://[\d]+(?:\.[\d]+)+:\d{1,5})([^>]*?>)`

	return ReplaceStringByRegex(src, regGetImgSrcRule, "")
}

// 连接postgre
func Connect(p *conf) (*gorm.DB, error) {

	dsn := fmt.Sprintf(
		"user=%s password=%s dbname=%s host=%s port=%s sslmode=disable TimeZone=Asia/Shanghai",
		p.User,
		p.Password,
		p.Name,
		p.Host,
		p.Port)

	db, err := gorm.Open(postgres.New(postgres.Config{
		DSN:                  dsn,
		PreferSimpleProtocol: true,
	}), &gorm.Config{
		SkipDefaultTransaction: true,
	})
	if err != nil {
		log.Fatal("数据库链接失败,%w", err)
		return nil, err
	}
	sqlDB, err := db.DB()
	if sqlDB == nil || err != nil {
		log.Fatal("数据库链接失败:调用DB对象失败,%w", err)
		return nil, err
	}
	if err = sqlDB.Ping(); err != nil {
		_ = sqlDB.Close()
		log.Fatal(err)
		return nil, err
	}
	return db, nil
}

// ==========================配置与结构体相关=============================================
// postgre数据库配置
type conf struct {
	User     string
	Password string
	Name     string
	Host     string
	Port     string
}

type Lab struct {
	Id         int64        `gorm:"column:id;primary_key;comment:ID"`
	Name       string       `gorm:"column:name;comment:名称"`
	Step       string       `gorm:"column:step;comment:"`
	CreateTime time.Time    `gorm:"column:create_time;comment:创建时间"`
	UpdateTime time.Time    `gorm:"column:update_time;comment:更新时间"`
	DeleteTime sql.NullTime `gorm:"column:delete_time;comment:删除时间"`
}

func (lab Lab) TableName() string {
	return "lab"
}
